package com.jwater.core.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jwater.org.apache.commons.io.IOUtils;
import jwater.org.springframework.util.StringUtils;

/**
 * @description:
 * @author: jnner
 * @createDate: 2021/2/5
 * @version: 1.0
 */
public class ProcessUtils {
    protected static Logger logger = LoggerFactory.getLogger(ProcessUtils.class);

    /**
     * 根据className去kill
     *
     * @param className
     * @param param
     * @param force
     */
    public static void killByClassName(String className, String param, boolean force) {
        List<Integer> ids = getProcessIds(className, param);
        for (Integer id : ids) {
            try {
                killById(id, force);
            } catch (Exception e) {
                logger.error(e.toString(), e);
            }
        }
    }

    /**
     * 根据进程id去kill
     *
     * @param id
     * @param force 是否强制
     * @throws Exception
     */
    public static void killById(int id, boolean force) throws Exception {
        if (force) {
            forceKillById(id);
        } else {
            killById(id);
        }
    }

    /**
     * 根据进程id去kill
     *
     * @param id
     * @throws Exception
     */
    private static void killById(int id) throws Exception {
        String type = SysType.getSysType();
        String cmd = "";
        if ("windows".equals(type)) {
            cmd = "cmd /c taskkill /F /PID " + id;
        } else if ("linux".equals(type)) {
            cmd = "kill " + id;
        }
        cmd(null, cmd);
    }

    /**
     * 强制杀死
     *
     * @param id
     * @throws Exception
     */
    private static void forceKillById(int id) throws Exception {
        String type = SysType.getSysType();
        String cmd = "";
        if ("windows".equals(type)) {
            cmd = "cmd /c taskkill /F /PID " + id;
        } else if ("linux".equals(type)) {
            cmd = "kill -9 " + id;
        }
        cmd(null, cmd);
    }

    /**
     * 获取进程编号
     *
     * @param className
     * @param param
     * @return
     */
    public static List<Integer> getProcessIds(String className, String param) {
        String type = SysType.getSysType();
        if (SysType.WINDOWS.equals(type)) {
            return getProcessIdsForWin(className, param);
        } else if (SysType.LINUX.equals(type)) {
            return getProcessIdsForLinux(className, param);
        }
        return getProcessIdsForWin(className, param);
    }

    /**
     * windows系统下获取进程编号
     *
     * @param className
     * @param param
     * @return
     */
    private static List<Integer> getProcessIdsForWin(String className, String param) {
        List<Integer> ids = new ArrayList<Integer>();
        try {
            String cmd = "jps -ml";
            List<String> lines = cmd(null, cmd);
            for (String line : lines) {
                String[] items = line.trim().split("\\s+");
                if (items.length >= 2 && items[1].equals(className)) {
                    if (!StringUtils.isEmpty(param)) {
                        if (line.indexOf(param) > -1) {
                            ids.add(Integer.parseInt(items[0]));
                        }
                    } else {
                        ids.add(Integer.parseInt(items[0]));
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return ids;
    }

    /**
     * linux系统下获取进程
     *
     * @param className
     * @param param
     * @return
     */
    private static List<Integer> getProcessIdsForLinux(String className, String param) {
        List<Integer> ids = new ArrayList<Integer>();
        try {
            String cmd = "ps -ef|grep " + className;
            if (!StringUtils.isEmpty(param)) {
                cmd += "|grep " + param;
            }
            List<String> lines = cmd(new String[]{"sh", "-c", cmd}, null);
            for (String line : lines) {
                if (line.indexOf("sh -c ps") == -1) {
                    String[] items = line.trim().split("\\s+");
                    ids.add(Integer.parseInt(items[1]));
                }
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return ids;
    }

    /**
     * 执行命令
     *
     * @param cmds
     * @param cmd
     * @return
     * @throws Exception
     */
    public static List<String> cmd(String[] cmds, String cmd) throws Exception {
        List<String> result = new ArrayList<String>();
        ProcessUtils.exeCmd(cmds, cmd, new ProcessCallback() {
            @Override
            public void onStop() {
            }

            @Override
            public void onMsg(String msg) {
                result.add(msg);
            }

            @Override
            public void onErrorMsg(String msg) {
                logger.error(msg);
            }
        }).msgThread.join();
        return result;
    }

    /**
     * 执行ClassName
     *
     * @param clazzName
     * @param jvmArgs
     * @param extLibDirs
     * @param args
     * @param callback
     * @return
     * @throws IOException
     */
    public static ProcessResult exeClazzName(String clazzName, String jvmArgs, List<String> extLibDirs, String args,
                                             ProcessCallback callback) throws IOException {
        String extdirs = System.getProperty("java.ext.dirs");
        extdirs = extdirs == null ? "" : extdirs;
        String type = SysType.getSysType();
        if (extLibDirs != null) {
            for (String ext : extLibDirs) {
                if (extdirs.indexOf(ext) == -1) {
                    if (!StringUtils.isEmpty(extdirs)) {
                        if (SysType.WINDOWS.equals(type)) {
                            extdirs += ";";
                        } else if (SysType.LINUX.equals(type)) {
                            extdirs += ":";
                        }
                    }
                    extdirs += ext;
                }
            }
        }
        if (SysType.WINDOWS.equals(type)) {
            extdirs = "\"" + extdirs + "\"";
        }
        String cmd = "java " + jvmArgs + " -Djava.ext.dirs=" + extdirs + " " + clazzName + " " + args;

        String[] cmds = null;
        if (callback == null) {
            if (SysType.WINDOWS.equals(type)) {
                cmd = "cmd /c start javaw " + jvmArgs + " -Djava.ext.dirs=" + extdirs + " " + clazzName + " " + args;
                cmds = new String[]{"cmd", "/c", cmd};
            } else {
                cmd = "nohup java " + jvmArgs + " -Djava.ext.dirs=" + extdirs + " " + clazzName + " " + args
                        + " >/dev/null 2>&1 &";
                cmds = new String[]{"sh", "-c", cmd};
            }
        } else {
            if (SysType.WINDOWS.equals(type)) {
                cmds = new String[]{"cmd", "/c", cmd};
            } else {
                cmds = new String[]{"sh", "-c", cmd};
            }
        }

        return exeCmd(cmds, null, callback);
    }

    /**
     * 执行命令
     *
     * @param cmds
     * @param cmd
     * @param callback
     * @return
     * @throws IOException
     */
    public static ProcessResult exeCmd(String[] cmds, String cmd, ProcessCallback callback) throws IOException {
        ProcessResult result = new ProcessResult();
        Process process = null;
        if (cmds == null) {
            process = Runtime.getRuntime().exec(cmd);
            logger.info("cmd==>" + cmd);
        } else {
            process = Runtime.getRuntime().exec(cmds);
            logger.info("cmd==>" + Arrays.toString(cmds));
        }
        result.process = process;
        if (callback != null) {
            result.msgThread = doCallback(callback, process.getInputStream(), false);
            result.errorThread = doCallback(callback, process.getErrorStream(), true);
        } else {
            if (cmd != null) {
                if (!cmd.startsWith("cmd /c start") && !cmd.startsWith("nohup")) {
                    logger.warn("not set InputStream thread for:" + cmd);
                }
            }
        }
        return result;
    }

    /**
     * 执行命令，可以设置环境变量
     *
     * @param cmdarray
     * @param envAppend
     * @param callback
     * @return
     * @throws IOException
     */
    public static ProcessResult exeCmdEnv(String[] cmdarray, ProcessEnvAppend envAppend, ProcessCallback callback)
            throws IOException {
        ProcessResult result = new ProcessResult();
        logger.info("cmd==>" + Arrays.toString(cmdarray));
        ProcessBuilder pb = new ProcessBuilder(cmdarray);
        Map<String, String> envs = pb.environment();
        if (envAppend != null) {
            envAppend.append(envs);
        }
        Process process = pb.start();
        if (callback != null) {
            result.msgThread = doCallback(callback, process.getInputStream(), false);
            result.errorThread = doCallback(callback, process.getErrorStream(), true);
        }
        return result;
    }

    /**
     * 执行className，可以设置环境变量
     *
     * @param clazzName
     * @param jvmArgs
     * @param extLibDirs
     * @param args
     * @param envAppend
     * @param callback
     * @return
     * @throws IOException
     */
    public static ProcessResult exeClazzNameEnv(String clazzName, String jvmArgs, List<String> extLibDirs, String args,
                                                ProcessEnvAppend envAppend,
                                                ProcessCallback callback) throws IOException {
        String extdirs = System.getProperty("java.ext.dirs");
        extdirs = extdirs == null ? "" : extdirs;
        String type = SysType.getSysType();
        if (extLibDirs != null) {
            for (String ext : extLibDirs) {
                if (extdirs.indexOf(ext) == -1) {
                    if (!StringUtils.isEmpty(extdirs)) {
                        if (SysType.WINDOWS.equals(type)) {
                            extdirs += ";";
                        } else if (SysType.LINUX.equals(type)) {
                            extdirs += ":";
                        }
                    }
                    extdirs += ext;
                }
            }
        }
        if (SysType.WINDOWS.equals(type)) {
            extdirs = "\"" + extdirs + "\"";
        }
        String cmd = "java " + jvmArgs + " -Djava.ext.dirs=" + extdirs + " " + clazzName + " " + args;
        String[] cmds = null;
        if (callback == null) {
            if (SysType.WINDOWS.equals(type)) {
                cmd = "start javaw " + jvmArgs + " -Djava.ext.dirs=" + extdirs + " " + clazzName + " " + args;
                cmds = new String[]{"cmd", "/c", cmd};
            } else {
                cmd = "nohup java " + jvmArgs + " -Djava.ext.dirs=" + extdirs + " " + clazzName + " " + args
                        + " >/dev/null 2>&1 &";
                cmds = new String[]{"sh", "-c", cmd};
            }
        } else {
            if (SysType.WINDOWS.equals(type)) {
                cmds = new String[]{"cmd", "/c", cmd};
            } else {
                cmds = new String[]{"sh", "-c", cmd};
            }
        }
        return exeCmdEnv(cmds, envAppend, callback);
    }

    /**
     * 结果回调
     *
     * @param callback
     * @param is
     * @param isError
     * @return
     */
    private static Thread doCallback(ProcessCallback callback, InputStream is, boolean isError) {
        Thread thread = new Thread(() -> {
            BufferedReader br = null;
            try {
                br = new BufferedReader(new InputStreamReader(is, getCharset()));
                String line;
                while ((line = br.readLine()) != null) {
                    if (isError) {
                        callback.onErrorMsg(line);
                    } else {
                        callback.onMsg(line);
                    }
                }
                if (!isError) {
                    callback.onStop();
                }
            } catch (Exception e) {
                logger.error(e.toString(), e);
            } finally {
                IOUtils.closeQuietly(br);
            }
        });
        thread.start();
        return thread;
    }

    /**
     * 字符编码
     *
     * @return
     */
    private static String getCharset() {
        String charset = "GBK";
        String type = SysType.getSysType();
        if (SysType.WINDOWS.equals(type)) {
            charset = "GBK";
        } else if (SysType.LINUX.equals(type)) {
            charset = "UTF-8";
        }
        return charset;
    }

    public static interface ProcessCallback {
        public void onMsg(String msg);

        public void onErrorMsg(String msg);

        public void onStop();
    }

    public static interface ProcessEnvAppend {
        public void append(Map<String, String> envs);
    }

    public static class ProcessResult {
        public Process process;
        public Thread msgThread;
        public Thread errorThread;

        public void stop() {
            process.destroy();
        }
    }
}
